local newt = reload("newt")
local delts = reload("delts")
local class = reload("class")
local super = reload("super.database")
local hiredisC = require("hiredisC")
local clear = reload("table.opt.clear")
local empty = reload("global.empty")
local temp = {}

---@diagnostic disable-next-line: duplicate-index
local keywords = {
    NX = 'NX',
    XX = 'XX',
    GT = 'GT',
    LT = 'LT',
    CH = 'CH',
    SUM = 'SUM',
    MIN = 'MIN',
    MAX = 'MAX',
    INCR = 'INCR',
    WEIGHTS = 'WEIGHTS',
    AGGREGETE = 'AGGREGETE',
}

---redis转lua
local rconver = {
    none = 1,  ---不变
    amap = 2,  ---map的数组形式
    ahap = 3,  ---map的数组形式-hmget 的返回值
    aset = 4,  ---set的数组形式
    rank = 4,  ---带分数的排行
    sint = 5,  ---字符串转整数
    xread = 6, ---xread的返回值
    scan = 7,  ---scan的返回值
}

---@class hiredisLC : database @hiredisLC类
local this = class(super)

function this:ctor(name, info)
    super.ctor(self)
    self._info = info
    self._name = name
    self._sha = {}
end

function this:kws()
    return keywords
end

function this:launch()
    super.launch(self)
    local info = self._info
    hiredisC.new(self._name, info.ip, info.port, info.db, info.auth, self)
    return self:hand()
end

---注册脚步
function this:regScript(carg)
    ---注册脚本
    local script = self._info.script
    local data = self._sha
    for k, v in pairs(script or empty) do
        self:script(v)(self.codata, self, data, k)
    end
    self:await(carg)
end

function this:callback(cookid)
    hiredisC.callback(cookid)
end

function this:decode(ireply, info, ...)
    ---追加返回参数
    if ireply == rconver.amap then
        self:arr1map(info.args, ...)
    elseif ireply == rconver.ahap then
        self:arr2map(info.args, info.fargs, ...)
    elseif ireply == rconver.rank then
        self:arr2rank(info.args, ...)
    elseif ireply == rconver.sint then
        self:str2int(info.args, ...)
    elseif ireply == rconver.xread then
        self:xread2ret(info.args, ...)
    elseif ireply == rconver.scan then
        self:arr2scan(info.args, ...)
    else
        self:nonepush(info.args, ...)
    end
end

function this:str2int(args, ...)
    local len = select("#", ...)
    for i = 1, len do
        local arg = select(i, ...)
        arg = tonumber(arg)
        table.insert(args, arg)
    end
end

function this:arr1map(args, arr)
    local len = #arr
    for i = 1, len, 2 do
        local key = arr[i]
        local val = arr[i + 1]
        arr[i] = nil
        arr[i + 1] = nil
        arr[key] = val
    end
    table.insert(args, arr)
end

function this:arr2map(args, keys, vals)
    local len = #keys
    for i = 1, len do
        local key = keys[i]
        local val = vals[i]
        vals[i] = nil
        keys[i] = nil
        vals[key] = val
    end
    table.insert(args, vals)
end

function this:arr2rank(args, arr)
    local member = newt()
    local scores = newt()
    local len = #arr
    for i = 1, len, 2 do
        table.insert(member, arr[i])
        table.insert(scores, arr[i + 1])
    end
    table.insert(args, member)
    table.insert(args, scores)
end

function this:xread2ret(args, ...)
    local len = select("#", ...)
    local arr = newt()
    local ids = newt()
    table.insert(args, arr)
    table.insert(args, ids)
    for i = 1, len do
        local group = select(i, ...)
        repeat
            ---消息队列里面没有消息
            if not group then
                break
            end

            ---消息队列里面解析结构
            for _, v in ipairs(group) do
                local key = v[1]
                local list = v[2]
                for _, inf in ipairs(list) do
                    local id = inf[1]
                    local js = inf[2]

                    local len = #js
                    local msg = newt(0, len)
                    for j = 1, len, 2 do
                        local k = js[j]
                        local v = js[j + 1]
                        msg[k] = v
                    end
                    table.insert(ids, id)
                    table.insert(arr, msg)
                end
            end
            delts(group)
        until true
    end
end

function this:arr2scan(args, arr)
    table.insert(args, arr[1])
    table.insert(args, arr[2])
end

---hash结构转hmset变长参数
function this:unhashpack(map)
    local args = clear(temp)
    for k, v in pairs(map) do
        table.insert(args, tostring(k))
        table.insert(args, tostring(v))
    end
    return table.unpack(args)
end

---stream 消息内容转变长参数
function this:unstreampack(map)
    return self:unhashpack(map)
end

---list参数转zadd成员参数
function this:list2zadd(list)
    local args = clear(temp)
    for _, v in ipairs(list) do
        table.insert(args, v)
        table.insert(args, v)
    end
    return table.unpack(args)
end

---set参数转zadd成员参数
function this:set2zadd(set)
    local args = clear(temp)
    for k, _ in pairs(set) do
        table.insert(args, k)
        table.insert(args, k)
    end
    return table.unpack(args)
end

function this:select(dbid)
    local cookid = self:cook()
    hiredisC.select(cookid, self._name, dbid)
    return self:watch(rconver.none)
end

function this:get(key)
    local cookid = self:cook()
    hiredisC.get(cookid, self._name, key)
    return self:watch(rconver.none)
end

function this:set(key, str)
    assert(self._info.write or false, "没有写入权限")
    local cookid = self:cook()
    hiredisC.set(cookid, self._name, key, str)
    return self:watch(rconver.none)
end

function this:del(...)
    assert(self._info.write or false, "没有写入权限")
    local cookid = self:cook()
    hiredisC.del(cookid, self._name, ...)
    return self:watch(rconver.none)
end

function this:ttl(key)
    local cookid = self:cook()
    hiredisC.ttl(cookid, self._name, key)
    return self:watch(rconver.none)
end

function this:pttl(key)
    local cookid = self:cook()
    hiredisC.pttl(cookid, self._name, key)
    return self:watch(rconver.none)
end

function this:persist(key)
    local cookid = self:cook()
    hiredisC.persist(cookid, self._name, key)
    return self:watch(rconver.none)
end

function this:expire(key, sec)
    local cookid = self:cook()
    hiredisC.expire(cookid, self._name, key, sec)
    return self:watch(rconver.none)
end

function this:pexpire(key, ms)
    local cookid = self:cook()
    hiredisC.pexpire(cookid, self._name, key, ms)
    return self:watch(rconver.none)
end

function this:expireat(key, sec)
    local cookid = self:cook()
    hiredisC.expireat(cookid, self._name, key, sec)
    return self:watch(rconver.none)
end

function this:pexpireat(key, ms)
    local cookid = self:cook()
    hiredisC.pexpireat(cookid, self._name, key, ms)
    return self:watch(rconver.none)
end

function this:type(key)
    local cookid = self:cook()
    hiredisC.type(cookid, self._name, key)
    return self:watch(rconver.none)
end

function this:keys(pattern)
    local cookid = self:cook()
    hiredisC.keys(cookid, self._name, pattern)
    return self:watch(rconver.none)
end

function this:randomkey()
    local cookid = self:cook()
    hiredisC.randomkey(cookid, self._name)
    return self:watch(rconver.none)
end

function this:rename(key, newkey)
    assert(self._info.write or false, "没有写入权限")
    local cookid = self:cook()
    hiredisC.rename(cookid, self._name, key, newkey)
    return self:watch(rconver.none)
end

function this:move(key, db)
    assert(self._info.write or false, "没有写入权限")
    local cookid = self:cook()
    hiredisC.move(cookid, self._name, key, db)
    return self:watch(rconver.none)
end

function this:sort(key, ...)
    local cookid = self:cook()
    hiredisC.sort(cookid, self._name, key, ...)
    return self:watch(rconver.none)
end

function this:hdel(key, ...)
    assert(self._info.write or false, "没有写入权限")
    local cookid = self:cook()
    hiredisC.hdel(cookid, self._name, key, ...)
    return self:watch(rconver.none)
end

function this:hlen(key)
    assert(self._info.write or false, "没有写入权限")
    local cookid = self:cook()
    hiredisC.hlen(cookid, self._name, key)
    return self:watch(rconver.none)
end

function this:hkeys(key)
    local cookid = self:cook()
    hiredisC.hkeys(cookid, self._name, key)
    return self:watch(rconver.none)
end

function this:hvals(key)
    local cookid = self:cook()
    hiredisC.hvals(cookid, self._name, key)
    return self:watch(rconver.none)
end

function this:hscan(key, cursor, match, count)
    local cookid = self:cook()
    hiredisC.hscan(cookid, self._name, key, cursor, "MATCH", match or "*", "COUNT", count)
    return self:watch(rconver.none)
end

function this:hget(key, field)
    local cookid = self:cook()
    hiredisC.hget(cookid, self._name, key, field)
    return self:watch(rconver.none)
end

function this:hmget(key, ...)
    local cookid = self:cook()
    hiredisC.hmget(cookid, self._name, key, ...)
    return self:watch(rconver.ahap, ...)
end

function this:hgetall(key)
    local cookid = self:cook()
    hiredisC.hgetall(cookid, self._name, key)
    return self:watch(rconver.amap)
end

function this:hset(key, ...)
    assert(self._info.write or false, "没有写入权限")
    local cookid = self:cook()
    hiredisC.hset(cookid, self._name, key, ...)
    return self:watch(rconver.none)
end

function this:hsetnx(key, ...)
    assert(self._info.write or false, "没有写入权限")
    local cookid = self:cook()
    hiredisC.hsetnx(cookid, self._name, key, ...)
    return self:watch(rconver.none)
end

function this:hincrby(key, field, increment)
    assert(self._info.write or false, "没有写入权限")
    local cookid = self:cook()
    hiredisC.hincrby(cookid, self._name, key, field, increment)
    return self:watch(rconver.none)
end

function this:hincrbyfloat(key, field, increment)
    assert(self._info.write or false, "没有写入权限")
    local cookid = self:cook()
    hiredisC.hincrbyfloat(cookid, self._name, key, field, increment)
    return self:watch(rconver.none)
end

function this:hmset(key, ...)
    assert(self._info.write or false, "没有写入权限")
    local cookid = self:cook()
    hiredisC.hmset(cookid, self._name, key, ...)
    return self:watch(rconver.none)
end

---hset map 字段
function this:htset(key, map)
    if (not map) or (not next(map)) then
        return
    end
    return self:hset(key, self:unhashpack(map))
end

---hset map 先删除再设置
function this:hcover(key, map)
    self:del(key)
    return self:htset(key, map)
end

function this:hexists(key, field)
    local cookid = self:cook()
    hiredisC.hexists(cookid, self._name, key, field)
    return self:watch(rconver.none)
end

function this:hrandfield(key, field)
    local cookid = self:cook()
    hiredisC.hrandfield(cookid, self._name, key, field)
    return self:watch(rconver.none)
end

function this:hstrlen(key, field)
    local cookid = self:cook()
    hiredisC.hstrlen(cookid, self._name, key, field)
    return self:watch(rconver.none)
end

function this:lpush(key, ...)
    assert(self._info.write or false, "没有写入权限")
    local cookid = self:cook()
    hiredisC.lpush(cookid, self._name, key, ...)
    return self:watch(rconver.none)
end

function this:rpush(key, ...)
    assert(self._info.write or false, "没有写入权限")
    local cookid = self:cook()
    hiredisC.rpush(cookid, self._name, key, ...)
    return self:watch(rconver.none)
end

function this:lpop(key)
    assert(self._info.write or false, "没有写入权限")
    local cookid = self:cook()
    hiredisC.lpop(cookid, self._name, key)
    return self:watch(rconver.none)
end

function this:rpop(key)
    assert(self._info.write or false, "没有写入权限")
    local cookid = self:cook()
    hiredisC.rpop(cookid, self._name, key)
    return self:watch(rconver.none)
end

function this:lrange(key, start, stop)
    local cookid = self:cook()
    hiredisC.lrange(cookid, self._name, key, start, stop)
    return self:watch(rconver.none)
end

function this:ltrim(key, start, stop)
    assert(self._info.write or false, "没有写入权限")
    local cookid = self:cook()
    hiredisC.ltrim(cookid, self._name, key, start, stop)
    return self:watch(rconver.none)
end

function this:sadd(key, ...)
    assert(self._info.write or false, "没有写入权限")
    local cookid = self:cook()
    hiredisC.sadd(cookid, self._name, key, ...)
    return self:watch(rconver.none)
end

function this:srem(key, ...)
    assert(self._info.write or false, "没有写入权限")
    local cookid = self:cook()
    hiredisC.srem(cookid, self._name, key, ...)
    return self:watch(rconver.none)
end

function this:spop(key, count)
    assert(self._info.write or false, "没有写入权限")
    local cookid = self:cook()
    hiredisC.spop(cookid, self._name, key, count)
    return self:watch(rconver.none)
end

function this:smove(sour, dest, member)
    assert(self._info.write or false, "没有写入权限")
    local cookid = self:cook()
    hiredisC.smove(cookid, self._name, sour, dest, member)
    return self:watch(rconver.none)
end

function this:scard(key)
    assert(self._info.write or false, "没有写入权限")
    local cookid = self:cook()
    hiredisC.scard(cookid, self._name, key)
    return self:watch(rconver.none)
end

function this:sismember(key, value)
    local cookid = self:cook()
    hiredisC.sismember(cookid, self._name, key, value)
    return self:watch(rconver.none)
end

function this:smembers(key)
    local cookid = self:cook()
    hiredisC.smembers(cookid, self._name, key)
    return self:watch(rconver.none)
end

function this:srandmember(key, count)
    local cookid = self:cook()
    hiredisC.srandmember(cookid, self._name, key, count)
    return self:watch(rconver.none)
end

function this:sdiff(key, ...)
    local cookid = self:cook()
    hiredisC.sdiff(cookid, self._name, key, ...)
    return self:watch(rconver.none)
end

function this:sinter(key, ...)
    local cookid = self:cook()
    hiredisC.sinter(cookid, self._name, key, ...)
    return self:watch(rconver.none)
end

function this:sunion(key, ...)
    local cookid = self:cook()
    hiredisC.sunion(cookid, self._name, key, ...)
    return self:watch(rconver.none)
end

function this:sunionstore(dest, ...)
    assert(self._info.write or false, "没有写入权限")
    local cookid = self:cook()
    hiredisC.sunionstore(cookid, self._name, dest, ...)
    return self:watch(rconver.none)
end

function this:sintertore(dest, ...)
    assert(self._info.write or false, "没有写入权限")
    local cookid = self:cook()
    hiredisC.sintertore(cookid, self._name, dest, ...)
    return self:watch(rconver.none)
end

function this:sdiffstore(dest, ...)
    assert(self._info.write or false, "没有写入权限")
    local cookid = self:cook()
    hiredisC.sdiffstore(cookid, self._name, dest, ...)
    return self:watch(rconver.none)
end

function this:zadd(key, ...)
    assert(self._info.write or false, "没有写入权限")
    local cookid = self:cook()
    hiredisC.zadd(cookid, self._name, key, ...)
    return self:watch(rconver.none)
end

function this:zrem(key, ...)
    assert(self._info.write or false, "没有写入权限")
    local cookid = self:cook()
    hiredisC.zrem(cookid, self._name, key, ...)
    return self:watch(rconver.none)
end

function this:zincrby(key, increment, member)
    assert(self._info.write or false, "没有写入权限")
    local cookid = self:cook()
    hiredisC.zincrby(cookid, self._name, key, increment, member)
    return self:watch(rconver.none)
end

function this:zcard(key)
    local cookid = self:cook()
    hiredisC.zcard(cookid, self._name, key)
    return self:watch(rconver.none)
end

function this:zrange(key, start, stop, ...)
    local cookid = self:cook()
    hiredisC.zrange(cookid, self._name, key, start, stop, ...)
    return self:watch(rconver.none)
end

function this:zrangebyscore(key, min, max, ...)
    local cookid = self:cook()
    hiredisC.zrangebyscore(cookid, self._name, key, min, max, ...)
    return self:watch(rconver.none)
end

function this:zrank(key, member)
    local cookid = self:cook()
    hiredisC.zrank(cookid, self._name, key, member)
    return self:watch(rconver.none)
end

function this:zcount(key, min, max)
    local cookid = self:cook()
    hiredisC.zcount(cookid, self._name, key, min, max)
    return self:watch(rconver.none)
end

function this:zscore(key, member)
    local cookid = self:cook()
    hiredisC.zscore(cookid, self._name, key, member)
    return self:watch(rconver.sint)
end

function this:zremrangebyrank(key, start, stop)
    local cookid = self:cook()
    hiredisC.zremrangebyrank(cookid, self._name, key, start, stop)
    return self:watch(rconver.none)
end

function this:zremrangebyscore(key, min, max)
    local cookid = self:cook()
    hiredisC.zremrangebyscore(cookid, self._name, key, min, max)
    return self:watch(rconver.none)
end

function this:zdiff(key, ...)
    local cookid = self:cook()
    hiredisC.sdiff(cookid, self._name, key, ...)
    return self:watch(rconver.none)
end

function this:zinter(key, ...)
    local cookid = self:cook()
    hiredisC.zinter(cookid, self._name, key, ...)
    return self:watch(rconver.none)
end

function this:zunion(key, ...)
    local cookid = self:cook()
    hiredisC.zunion(cookid, self._name, key, ...)
    return self:watch(rconver.none)
end

function this:zdiffstore(dest, keys, ...)
    assert(self._info.write or false, "没有写入权限")
    local cookid = self:cook()
    hiredisC.zdiffstore(cookid, self._name, dest, keys, ...)
    return self:watch(rconver.none)
end

function this:zinterstore(dest, keys, ...)
    assert(self._info.write or false, "没有写入权限")
    local cookid = self:cook()
    hiredisC.zinterstore(cookid, self._name, dest, keys, ...)
    return self:watch(rconver.none)
end

function this:zunionstore(dest, keys, ...)
    assert(self._info.write or false, "没有写入权限")
    local cookid = self:cook()
    hiredisC.zunionstore(cookid, self._name, dest, keys, ...)
    return self:watch(rconver.none)
end

function this:xadd(key, ...)
    assert(self._info.write or false, "没有写入权限")
    local cookid = self:cook()
    hiredisC.xadd(cookid, self._name, key, ...)
    return self:watch(rconver.none)
end

function this:xgroup(...)
    local cookid = self:cook()
    hiredisC.xgroup(cookid, self._name, ...)
    return self:watch(rconver.none)
end

function this:xread(...)
    local cookid = self:cook()
    hiredisC.xread(cookid, self._name, ...)
    return self:watch(rconver.xread)
end

function this:xreadgroup(...)
    local cookid = self:cook()
    hiredisC.xreadgroup(cookid, self._name, ...)
    return self:watch(rconver.xread)
end

function this:xack(key, group, ...)
    local cookid = self:cook()
    hiredisC.xack(cookid, self._name, key, group, ...)
    return self:watch(rconver.none)
end

function this:xinfo(...)
    local cookid = self:cook()
    hiredisC.xinfo(cookid, self._name, ...)
    return self:watch(rconver.none)
end

function this:xdel(key, ...)
    assert(self._info.write or false, "没有写入权限")
    local cookid = self:cook()
    hiredisC.xdel(cookid, self._name, key, ...)
    return self:watch(rconver.none)
end

function this:xrange(key, start, stop, ...)
    local cookid = self:cook()
    hiredisC.xrange(cookid, self._name, key, start, stop, ...)
    return self:watch(rconver.none)
end

function this:xrevrange(key, start, stop, ...)
    local cookid = self:cook()
    hiredisC.xrevrange(cookid, self._name, key, start, stop, ...)
    return self:watch(rconver.none)
end

function this:xtrim(key, maxlen, ...)
    assert(self._info.write or false, "没有写入权限")
    local cookid = self:cook()
    hiredisC.xtrim(cookid, self._name, key, maxlen, ...)
    return self:watch(rconver.none)
end

function this:xclaim(key, group, consumer, min_idle_time, id, ...)
    local cookid = self:cook()
    hiredisC.xclaim(cookid, self._name, key, group, consumer, min_idle_time, id, ...)
    return self:watch(rconver.none)
end

function this:xpending(key, group, ...)
    local cookid = self:cook()
    hiredisC.xpending(cookid, self._name, key, group, ...)
    return self:watch(rconver.none)
end

---消息分组
---@param key string @消息队列名称
---@param groupname string @消费组名称
function this:xnewMsg(key, groupname)
    assert(self._info.write or false, "没有写入权限")
    self._xkey = key
    return self:xgroup(
        "CREATE",            ---创建消费组
        key,                 ---消息队列名称
        groupname or "recv", ---消费者名称
        0,                   ---消费者组起始ID
        "MKSTREAM")          ---MKSTREAM表示如果不存在消息队列则创建
end

---消息添加
---@param map table @消息内容
---@param key string @消息队列名称
function this:xaddMsg(map, key)
    assert(self._info.write or false, "没有写入权限")
    return self:xadd(
        key or self._xkey, ---消息队列名称
        "MAXLEN",          ----消息队列长度限制
        "~",               ---消息队列长度限制长度
        100000,            ---消息队列长度限制数量
        "*",               ---消息ID
        self:unstreampack(map))
end

---获取消息
---@param key string @消息队列名称
function this:xgetMsg(count, groupName, consumerName)
    return self:xreadgroup(
        "GROUP",                ---指定消费组名称和消费者名称
        groupName or "recv",    ---消息组名称
        consumerName or "read", ---消费者名称
        "COUNT", count or 100,  ---读取条数
        "STREAMS",              ---指定读取一个或者多个流键
        self._xkey,             ---指定流键
        ">")                    ---表示只接收从未被当前消费者组中任何消费者传递过的消息
end

---确认消息
---@param ids string[] @消息ID列表
---@param consumerName string|nil @消费者名称
function this:xackMsg(ids, consumerName)
    return self:xack(self._xkey, consumerName or "recv", table.unpack(ids))
end

function this:scan(cursor, pattern, count)
    local cookid = self:cook()
    hiredisC.scan(cookid, self._name, cursor, "MATCH", pattern, "COUNT", count)
    return self:watch(rconver.scan)
end

function this:script(fpath)
    local cookid = self:cook()
    hiredisC.script(cookid, self._name, "LOAD", reload(fpath))
    return self:watch(rconver.none)
end

function this:evalsha(key, kcnt, ...)
    local sha = self._sha[key]
    if not sha then
        error("not find script sha")
    end
    local cookid = self:cook()
    hiredisC.evalsha(cookid, self._name, sha, kcnt, ...)
    return self:watch(rconver.none)
end

function this:eval(fpath, kcnt, ...)
    local cookid = self:cook()
    local script = reload(fpath)
    hiredisC.eval(cookid, self._name, script, kcnt, ...)
    return self:watch(rconver.none)
end

function this:notkey(fpath, ...)
    return self:eval(fpath, 0, ...)
end

function this:onekey(fpath, key, ...)
    return self:eval(fpath, 1, key, ...)
end

function this:await(carg)
    self:type("")
    (super.coback, self, carg)
    return super.await(self)
end

return this
